package org.vs.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.util.Base64;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.vs.constant.RedisConstant;
import org.vs.contract.VoteContract;
import org.vs.domain.bo.UserVoteHashBO;
import org.vs.domain.dto.PublishDTO;
import org.vs.domain.dto.VoteDTO;
import org.vs.domain.dto.VoteInfoDTO;
import org.vs.domain.model.SysUser;
import org.vs.domain.model.TOption;
import org.vs.domain.model.TUserVote;
import org.vs.domain.model.TVote;
import org.springframework.stereotype.Service;
import org.vs.domain.vo.OptionResultVO;
import org.vs.domain.vo.VoteInfoVO;
import org.vs.domain.vo.VoteVO;
import org.vs.handler.ClassLoaderHandler;
import org.vs.handler.responsibilitychain.vote.VotePublishGethHandler;
import org.vs.handler.responsibilitychain.vote.VotePublishHandlerEO;
import org.vs.mapper.SysUserMapper;
import org.vs.mapper.TOptionMapper;
import org.vs.mapper.TUserVoteMapper;
import org.vs.mapper.TVoteMapper;
import org.vs.result.RestResult;
import org.vs.result.RestResultBuilder;
import org.vs.service.SSEService;
import org.vs.service.TOptionService;
import org.vs.service.TUserVoteService;
import org.vs.service.TVoteService;
import org.vs.util.JwtUtil;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.tx.gas.StaticGasProvider;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @author Fall
 * @description 针对表【t_vote】的数据库操作Service实现
 * @createDate 2024-01-25 22:54:55
 */
@Slf4j
@Service
@AllArgsConstructor
public class TVoteServiceImpl extends ServiceImpl<TVoteMapper, TVote>
        implements TVoteService {

    private TVoteMapper voteMapper;

    private TOptionMapper optionMapper;

    private TOptionService optionService;

    private TUserVoteService userVoteService;

    private TUserVoteMapper userVoteMapper;

    private SysUserMapper userMapper;

    private RedisTemplate redisTemplate;

    private SSEService sseService;

    private VotePublishGethHandler votePublishGethHandler;

    private ClassLoaderHandler classLoaderHandler;

    private Web3j web3j;

    private StaticGasProvider gasProvider;

    @Override
    public RestResult all(Integer page, Integer pageSize, String title, String authorization) {
        if (page < 0 || pageSize < 0) {
            return new RestResultBuilder().fail("数据不齐");
        }

        SysUser user = JwtUtil.getUser(authorization);
        List<TVote> votes = voteMapper.selectAllByTitleAndUserIdAndAddress(title, user.getUserId(), page * pageSize + page, pageSize);
        Long size = voteMapper.countSelectAllByTitleAndUserIdAndAddress(title, user.getUserId());

        VoteVO voteVO = VoteVO
                .builder()
                .data(votes)
                .size(size)
                .build();

        return new RestResultBuilder().success(voteVO);
    }

    @Override
    public RestResult info(Long id, String authorization) {
        if (id == null) {
            return new RestResultBuilder().fail("数据不齐");
        }

        VoteInfoVO voteInfo = new VoteInfoVO();

        TVote vote = voteMapper.selectById(id);
        BeanUtils.copyProperties(vote, voteInfo);

        voteInfo.setOptions(optionMapper.selectList(new QueryWrapper<TOption>()
                .eq("vote_id", id)));

        SysUser user = JwtUtil.getUser(authorization);
        // 判断是不是自己发布的投票
        if (user.getUserId().equals(vote.getUserId())) {
            voteInfo.setUsers(voteMapper.selectUserVOInVoteByVoteId(id));
        }

        // 如果是自己的且在没有发布的情况下可以修改
        voteInfo.setCanUpdate(user.getUserId().equals(vote.getUserId()) && vote.getAddress() == null);

        return new RestResultBuilder().success(voteInfo);
    }

    @Override
    public RestResult save(VoteInfoDTO voteInfoDTO, String authorization) {
        TVote vote = new TVote();

        BeanUtils.copyProperties(voteInfoDTO, vote);

        if (voteInfoDTO.getStartTime().indexOf("T") != -1) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.CHINA);

            vote.setStartTime(LocalDateTime.parse(voteInfoDTO.getStartTime(), formatter));
            vote.setEndTime(LocalDateTime.parse(voteInfoDTO.getEndTime(), formatter));
        } else {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);

            vote.setStartTime(LocalDateTime.parse(voteInfoDTO.getStartTime(), formatter));
            vote.setEndTime(LocalDateTime.parse(voteInfoDTO.getEndTime(), formatter));
        }

        SysUser user = JwtUtil.getUser(authorization);
        vote.setUserId(user.getUserId());

        Long voteId = 0L;
        if (vote.getVoteId() != null) {
            voteId = vote.getVoteId();
            voteMapper.updateById(vote);
        } else {
            voteMapper.insertAndGetId(vote);
            voteId = vote.getVoteId();
        }

        Long finalVoteId = voteId;
        List<TOption> options = new ArrayList<>();
        voteInfoDTO.getOptions().stream().forEach(v -> {
            options.add(TOption
                    .builder()
                    .optionId(v.getOptionId())
                    .voteId(finalVoteId)
                    .description(v.getDescription())
                    .image(v.getImage())
                    .name(v.getName())
                    .build());
        });
        optionService.saveOrUpdateBatch(options);

        List<TUserVote> userVotes = new ArrayList<>();
        userVoteMapper.delete(new QueryWrapper<TUserVote>()
                .eq("vote_id", finalVoteId));
        voteInfoDTO.getUsers().stream().forEach(v -> {
            userVotes.add(TUserVote
                    .builder()
                    .userId(v.getUserId())
                    .voteId(finalVoteId)
                    .build());
        });
        userVoteService.saveOrUpdateBatch(userVotes);

        return new RestResultBuilder().success();
    }

    @Override
    public RestResult delete(Long id) {
        TVote vote = voteMapper.selectById(id);

        // 如果存在 区块链的地址
        // 需要关闭 区块链中的投票直接 cut down
        if (vote.getAddress() != null) {

        }

        voteMapper.deleteById(id);

        return new RestResultBuilder().success();
    }

    @Override
    public RestResult publish(PublishDTO publishDTO, String authorization) throws Exception {
        TVote vote = voteMapper.selectById(publishDTO.getId());

        if (vote.getAddress() != null) {
            return new RestResultBuilder().fail("已经发布 无需重复发布");
        }

        SysUser user = JwtUtil.getUser(authorization);

        Credentials credentials =
                JSON.parseObject(
                        redisTemplate.opsForValue().get(RedisConstant.USER_REDIS_CREDENTIAL_KEY + user.getUserId()).toString(),
                        Credentials.class);

        votePublishGethHandler.handler(VotePublishHandlerEO
                .builder()
                .credentials(credentials)
                .publishDTO(publishDTO)
                .hashcode(publishDTO.getHashcode())
                .build());

        sseService.delete(publishDTO.getHashcode());

        return new RestResultBuilder().success();
    }

    @Override
    public RestResult vote(VoteDTO voteDTO, String authorization) throws Exception {
        SysUser user = JwtUtil.getUser(authorization);
        user = userMapper.selectById(user.getUserId());
        Credentials credentials =
                JSON.parseObject(
                        redisTemplate.opsForValue().get(RedisConstant.USER_REDIS_CREDENTIAL_KEY + user.getUserId()).toString(),
                        Credentials.class);
        TVote vote = voteMapper.selectById(voteDTO.getVoteId());
        TUserVote userVote = userVoteMapper.selectOne(new LambdaQueryWrapper<TUserVote>()
                .eq(TUserVote::getUserId, user.getUserId())
                .eq(TUserVote::getVoteId, vote.getVoteId()));
        VoteContract voteContract = VoteContract
                .load(vote.getAddress(), web3j, credentials, gasProvider);

        List<List<Object>> list = (List<List<Object>>) voteContract.getElectors().send();
        List<VoteContract.Elector> electors =
                classLoaderHandler.loader(list, VoteContract.Elector.class);

        boolean flag = false;
        VoteContract.Elector tempElector = null;
        for (VoteContract.Elector elector : electors) {
            flag = verify(user.getAddress(), userVote.getHashcode(), elector.sign);
            if (flag) {
                tempElector = elector;
                break;
            }
        }

        if (!flag) {
            return new RestResultBuilder().fail("无权限投票");
        }

        String hash =
                voteContract.vote(tempElector.sign, BigInteger.valueOf(voteDTO.getOptionId())).send().getTransactionHash();
        log.info("投票交易 hash {}", hash);
        return new RestResultBuilder().success();
    }

    @Override
    public RestResult result(Long id, String authorization) throws Exception {
        TVote vote = voteMapper.selectById(id);
        SysUser user = JwtUtil.getUser(authorization);
        Credentials credentials =
                JSON.parseObject(
                        redisTemplate.opsForValue().get(RedisConstant.USER_REDIS_CREDENTIAL_KEY + user.getUserId()).toString(),
                        Credentials.class);
        VoteContract voteContract = VoteContract
                .load(vote.getAddress(), web3j, credentials, gasProvider);
        List<List<Object>> list = (List<List<Object>>) voteContract.voteResult().send();
        List<VoteContract.VoteResultVo> voteResultVos =
                classLoaderHandler.loader(list, VoteContract.VoteResultVo.class);

        List<Long> optionIds = new ArrayList<>();
        Map<Long, Integer> optionMap = new HashMap<>();
        for (VoteContract.VoteResultVo voteResultVo : voteResultVos) {
            optionIds.add(voteResultVo.id.longValue());
            optionMap.put(voteResultVo.id.longValue(), voteResultVo.votes.intValue());
        }

        List<TOption> options = optionMapper.selectList(new QueryWrapper<TOption>()
                .in("option_id", optionIds));
        List<OptionResultVO> optionResultVOS = new ArrayList<>();
        for (TOption option : options) {
            optionResultVOS.add(OptionResultVO
                    .builder()
                    .name(option.getName())
                    .votes(optionMap.get(option.getOptionId()))
                    .build());
        }

        return new RestResultBuilder().success(optionResultVOS);
    }

    @Override
    public RestResult hashMessage(String authorization) {
        SysUser user = JwtUtil.getUser(authorization);

        List<UserVoteHashBO> list = userVoteMapper.hashMessage(user.getUserId());

        return new RestResultBuilder().success(list);
    }

    /**
     * 验证签名
     *
     * @param data
     * @param publicKey
     * @param sign
     * @return
     */
    private boolean verify(String data, String publicKey, String sign) {
        try {
            byte[] dataBytes = data.getBytes("UTF-8");
            byte[] keyBytes = Base64.decodeBase64(publicKey);
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey1 = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(publicKey1);
            signature.update(dataBytes);
            return signature.verify(Base64.decodeBase64(sign));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
